前面介紹了樂觀鎖的原理,這裡將介紹在 postgres 中,是如何利用樂觀鎖的原理來建構 MVCC DB 的。
我們先建立好一張table叫table1,裡面只有name這個欄位。
CREATE TABLE table1 (
"name" varchar NOT NULL
);
其實 Postgres 每個表中都有一些隱藏欄位,
我們可以嘗是在任何一張 table query 出那些欄位
select ctid,xmin,xmax,cmin,cmax,id from XXX_TABLE
欄位 | 意思 |
---|---|
ctid | 在表中的邏輯位置 |
xmin | born 建立 record 時,當前 tx 的 id |
xmax | die 刪除 record 時,當前 tx 的 id,default=0 |
cmin | 插入事務中的命令標識符 |
cmax | 刪除交易中的命令標識符 |
ctid: 可以幫我們看一下是否有新的 record 建立出來
xmin: 看該筆 record 是由哪個 tx 所建立的,這裡填上 txId
xmax: 看該筆 record 是由哪個 tx 所刪除的,這裡填上 txId
cmin:插入事務中的命令標識符(從 0 開始)
cmax:刪除交易中的命令標識符(從 0 開始)
後續的介紹我們這裡只需要特別注意ctid與xmax,xmin這三個key即可。
我們知道每次的操作都是一個 transaction,那麼該如何得知目前的 transactionId 呢?
可以下select txid_current()
大概知道上面各個欄位做什麼後,接下來開始解析 CRUD 的操作會有什麼變化吧!
先來看看insert後會有什麼變化
begin;
SELECT txid_current(); --735
insert into table1 (name) values ('a' );
select *,ctid,xmin,xmax from table1;
commit;
name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 0 |
可以看到ctid與xmin被賦予了值,而xmin就是insert資料的txid |
begin;
select *,ctid,xmin,xmax from table1;
commit;
name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 0 |
除了SELECT txid_current(); 之外, |
|||
一般的select都不會被assign一個txid. | |||
因為沒有做任何修改(write). |
--tx1
begin;
SELECT txid_current(); --754
DELETE FROM table1 WHERE name = 'a';
因為delete commit後就無法利用一般的select查到,
所以我們趁著還沒commit之前,開另外一個tx來做select。
--tx2
select *,ctid,xmin,xmax from table1;
name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 754 |
可以看到xmax被賦予了值,而xmax就是DELETE資料的txid。
--tx1
begin;
SELECT txid_current(); --755
UPDATE table1 set name='uu' where name='a';
select *,ctid,xmin,xmax from table1;
name | ctid | xmin | xmax |
---|---|---|---|
uu | (0,2) | 755 | 0 |
可以看到ctid為(0,2),xmin為755,就如同insert一筆新的紀錄。 |
因為UPDATE commit後就無法利用一般的select查被標誌上delete的紀錄,
我們趁著還沒commit之前,開tx2來做select。
--tx2
select *,ctid,xmin,xmax from table1;
name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 755 |
可以看到ctid(0,1)仍在,xmax被標上755,就如同DELET紀錄般。 |
所以update的動作我們可以被視為:
insert一筆新紀錄+delete一筆舊紀錄。
至於rollback呢?
我們就拿剛剛還沒COMMIT的UPDATE來做ROLLBACK.
然後再select *,ctid,xmin,xmax from table1;
name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 755 |
ctid(0,1)雖然沒有被刪除掉也仍然可以被查看,但是他的xmax居然還有755的標記,這是什麼原因呢?我們先繼續往下看。
我們再新增一筆紀錄insert into table1 (name) values ('b');
name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 755 |
b | (0,3) | 755 | 0 |
ctid從(0,3)開始了,所以ctid(0,2)的插入也成功rollback了. |
rollback沒看到ctid(0,2)很合理,但是ctid(0,1)的xmax被填上了755,所以他到底是被刪除了還是還沒呢?
這時就要討論一下postgres MVCC的可見性判斷了。
邏輯大概是這樣
每個有做過write的tx都會有一個自己的txid.
而如果只有xmin代表新增的紀錄,如果同時有xmin與xmax代表被刪除的紀錄.
每一筆的紀錄,在操作時,都會被記錄到一個clog的檔案之中,
用來表示該筆紀錄是否有被其他tx正在做修改。
在每個tx的執行之中,都會在每筆紀錄clog,來表示該表紀錄正處於什麼樣的狀態之中。
#define TRANSACTION_STATUS_IN_PROGRESS=0x00 正在進行中
#define TRANSACTION_STATUS_COMMITTED=0x01 已提交(commit)
#define TRANSACTION_STATUS_ABORTED=0x02 已回滾(rollback)
#define TRANSACTION_STATUS_SUB_COMMITTED=0x03 子事務已提交
主要會先判斷clog,
txid
>xmin or xmax
:代表舊有的tx,該是什麼樣就什麼樣。txid
=xmin or xmax
:代表目前自己操作的tx,該是什麼樣就什麼樣。txid
<xmin or xmax
:代表未來的tx,不可見。txid
>xmin or xmax
:代表舊有的tx,該是什麼樣就不要
什麼樣。txid
=xmin or xmax
:代表目前自己操作的tx,該是什麼樣就不要
什麼樣。txid
<xmin or xmax
:代表未來的tx,不可見。name | ctid | xmin | xmax |
---|---|---|---|
a | (0,1) | 753 | 755 |
b | (0,3) | 755 | 0 |
的clog這兩筆紀錄被標註上了TRANSACTION_STATUS_ABORTED,
因此,代表著被新增的要不可見,被刪除的要可見。
postgres 透過 xmin/xmax 與 commit 的狀態來判斷是否為該 tx 可以用的資料。
我們可看到當 rollback 與刪除時,這些標記都還在,頻繁操作的表會累積大量標記,佔用硬碟空間,
postgres 的解決方法是提供 vacuum 命令來清理過期的數據,也會在後續的單元中介紹。